﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Linq;

namespace ToyundaPhone.Helpers
{
	public static class HighlightText
	{
		public static string GetText(TextBlock target)
		{
			return target.GetValue(TextProperty) as string;
		}

		public static void SetText(TextBlock target, string value)
		{
			target.SetValue(TextProperty, value);
		}

		public static readonly DependencyProperty TextProperty =
			DependencyProperty.RegisterAttached("Text", typeof(string), typeof(HighlightText), new PropertyMetadata(string.Empty, (sender, args) =>
				{
					if ((args.NewValue == args.OldValue) ||
						((args.NewValue != null) && (args.OldValue != null) && (string.Equals(args.OldValue as string, args.NewValue as string))))
						return;
					UpdateInlines(sender as TextBlock, args.NewValue as string, null, null, null);
				}));

		public static string GetKeywords(TextBlock target)
		{
			return target.GetValue(KeywordsProperty) as string;
		}

		public static void SetKeywords(TextBlock target, string value)
		{
			target.SetValue(KeywordsProperty, value);
		}

		public static readonly DependencyProperty KeywordsProperty =
			DependencyProperty.RegisterAttached("Keywords", typeof(string), typeof(HighlightText), new PropertyMetadata(string.Empty, (sender, args) =>
				{
					if ((args.NewValue == args.OldValue) ||
						((args.NewValue != null) && (args.OldValue != null) && (string.Equals(args.OldValue as string, args.NewValue as string))))
						return;
					UpdateInlines(sender as TextBlock, null, args.NewValue as string, null, null);
				}));

		public static ITextSplitter GetTextSplitter(TextBlock target)
		{
			return target.GetValue(TextSplitterProperty) as ITextSplitter;
		}

		public static void SetTextSplitter(TextBlock target, ITextSplitter value)
		{
			target.SetValue(TextSplitterProperty, value);
		}

		public static readonly DependencyProperty TextSplitterProperty =
			DependencyProperty.RegisterAttached("TextSplitter", typeof(ITextSplitter), typeof(HighlightText), new PropertyMetadata(null, (sender, args) =>
				{
					if (args.NewValue == args.OldValue)
						return;
					UpdateInlines(sender as TextBlock, null, null, args.NewValue as ITextSplitter, null);
				}));

		public static ToyundaPhone.Database.IWordExtractor GetWordExtractor(TextBlock target)
		{
			return target.GetValue(WordExtractorProperty) as ToyundaPhone.Database.IWordExtractor;
		}

		public static void SetWordExtractor(TextBlock target, ToyundaPhone.Database.IWordExtractor value)
		{
			target.SetValue(WordExtractorProperty, value);
		}

		public static readonly DependencyProperty WordExtractorProperty =
			DependencyProperty.RegisterAttached("WordExtractor", typeof(ToyundaPhone.Database.IWordExtractor), typeof(HighlightText), new PropertyMetadata(null, (sender, args) =>
			{
				if (args.NewValue == args.OldValue)
					return;
				UpdateInlines(sender as TextBlock, null, null, null, args.NewValue as ToyundaPhone.Database.IWordExtractor);
			}));

		public static HighlightStyle GetHighlightStyle(TextBlock target)
		{
			return target.GetValue(HighlightStyleProperty) as HighlightStyle;
		}

		public static void SetHighlightStyle(TextBlock target, HighlightStyle value)
		{
			target.SetValue(HighlightStyleProperty, value);
		}

		public static readonly DependencyProperty HighlightStyleProperty =
			DependencyProperty.RegisterAttached("HighlightStyle", typeof(HighlightStyle), typeof(HighlightText), new PropertyMetadata(null));

		private static void UpdateInlines(TextBlock target, string text, string keywords, ITextSplitter splitter, ToyundaPhone.Database.IWordExtractor extractor)
		{
			if (target == null) return;
			if (text == null) text = GetText(target);
			if (keywords == null) keywords = GetKeywords(target);
			if (splitter == null) splitter = GetTextSplitter(target);
			if (extractor == null) extractor = GetWordExtractor(target);
			HighlightStyle style = GetHighlightStyle(target);
			if ((splitter == null) || (extractor == null) || (string.IsNullOrWhiteSpace(keywords)))
			{
				target.Inlines.Clear();
				target.Inlines.Add(MakeRun(text));
				return;
			}
			IEnumerable<Inline> elements = BuildInlines(text, keywords, splitter, extractor, style);
			target.Inlines.Clear();
			foreach (var inline in elements)
			{
				target.Inlines.Add(inline);
			}
		}

		private static IEnumerable<Inline> BuildInlines(string text, string keywords, ITextSplitter splitter, ToyundaPhone.Database.IWordExtractor extractor, HighlightStyle style)
		{
			IEnumerable<string> words = extractor.GetWords(keywords);
			List<Run> runs = new List<Run>();
			System.Text.StringBuilder notMatching = new System.Text.StringBuilder(text.Length);
			Queue<int> equalLengthMatchs = new Queue<int>();
			foreach (var token in splitter.GetTokens(text))
			{
				if (token.IsMatch)
				{
					var match = (from w in words where token.Value.StartsWith(w, StringComparison.InvariantCultureIgnoreCase) orderby w.Length descending select w).FirstOrDefault();
					if (match != null)
					{
						if (notMatching.Length > 0)
						{
							runs.Add(MakeRun(notMatching.ToString()));
							notMatching.Clear();
						}
						runs.Add(MakeRun(token.Value.Substring(0, match.Length), style));
						if (token.Value.Length != match.Length)
						{
							notMatching.Append(token.Value.Substring(match.Length));
						}
						else
						{
							equalLengthMatchs.Enqueue(runs.Count - 1);
						}
					}
					else
					{
						notMatching.Append(token.Value);
					}
				}
				else
				{
					notMatching.Append(token.Value);
				}
			}
			if (notMatching.Length > 0)
			{
				runs.Add(MakeRun(notMatching.ToString()));
			}
			if (equalLengthMatchs.Count > 0)
			{
				var tokens = splitter.GetTokens(keywords).ToArray();
				while (equalLengthMatchs.Count > 0)
				{
					var runIndex = equalLengthMatchs.Dequeue();
					if (runIndex + 2 >= runs.Count) continue;
					for (int i = 0; (i + 2) < tokens.Length; i++)
					{
						bool equal = true;
						for (int j = 0; j < 3; j++)
						{
							equal = equal && (StringComparer.InvariantCultureIgnoreCase.Compare(runs[runIndex + j].Text, tokens[i + j].Value) == 0);
						}
						if (equal)
						{
							style.Apply(runs[runIndex + 1]);
						}
					}
				}
			}
			return runs.Cast<Inline>();
		}

		private static Run MakeRun(string text, HighlightStyle style = null)
		{
			Run current = new Run() { Text = text };
			if (style != null)
			{
				style.Apply(current);
			}
			return current;
		}
	}
}
